"""Generate architecture diagrams with Chinese labels using matplotlib."""
from __future__ import annotations

from pathlib import Path
from typing import Iterable

import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch
from matplotlib.font_manager import FontProperties
from PIL import Image

BASE_DIR = Path(__file__).resolve().parent
PROJECT_DIR = BASE_DIR
REPORT_DIR = PROJECT_DIR / '报告'
IMAGE_DIR = REPORT_DIR / '配图'
IMAGE_DIR.mkdir(parents=True, exist_ok=True)

SIMHEI_PATH = Path('/mnt/c/Windows/Fonts/simhei.ttf')
DEFAULT_FONT = FontProperties(fname=str(SIMHEI_PATH)) if SIMHEI_PATH.exists() else FontProperties()
plt.rcParams['axes.edgecolor'] = '#333333'
plt.rcParams['axes.linewidth'] = 0.8


def _create_canvas(width: float = 12, height: float = 7):
    fig, ax = plt.subplots(figsize=(width, height))
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 10)
    ax.axis('off')
    return fig, ax


def _add_box(
    ax,
    xy: tuple[float, float],
    size: tuple[float, float],
    text_lines: Iterable[str],
    facecolor: str,
    edgecolor: str,
    linewidth: float = 1.8,
    radius: float = 0.2,
):
    x, y = xy
    width, height = size
    patch = FancyBboxPatch(
        (x, y),
        width,
        height,
        boxstyle=f"round,pad=0.02,rounding_size={radius}",
        linewidth=linewidth,
        edgecolor=edgecolor,
        facecolor=facecolor,
    )
    ax.add_patch(patch)
    lines = list(text_lines)
    if not lines:
        return patch
    line_height = 0.35
    total = (len(lines) - 1) * line_height
    center_y = y + height / 2 + total / 2
    for idx, line in enumerate(lines):
        ax.text(
            x + width / 2,
            center_y - idx * line_height,
            line,
            ha='center',
            va='center',
            fontproperties=DEFAULT_FONT,
            fontsize=14,
            color='#222222',
        )
    return patch


def _add_arrow(ax, start: tuple[float, float], end: tuple[float, float], color: str = '#555555'):
    arrow = FancyArrowPatch(
        start,
        end,
        arrowstyle='->',
        mutation_scale=18,
        linewidth=2,
        color=color,
    )
    ax.add_patch(arrow)


def _save(fig, name: str):
    out_base = IMAGE_DIR / name
    png_path = out_base.with_suffix('.png')
    svg_path = out_base.with_suffix('.svg')
    bmp_path = out_base.with_suffix('.bmp')
    fig.savefig(png_path, dpi=200, bbox_inches='tight')
    fig.savefig(svg_path, dpi=200, bbox_inches='tight')
    with Image.open(png_path) as img:
        img.save(bmp_path)
    plt.close(fig)


def system_flow():
    fig, ax = _create_canvas()
    _add_box(ax, (0.4, 7.0), (2.0, 1.2), ['学生终端', 'Web / 小程序'], '#F0F8FF', '#1E90FF')
    _add_box(ax, (3.0, 7.0), (2.2, 1.2), ['统一网关', '(JWT + 限流)'], '#F8FFF8', '#20B2AA')

    services = [
        ('认证服务', 5.8, 8.2),
        ('课表服务', 5.8, 6.4),
        ('知识库', 5.8, 4.6),
        ('问答引擎', 5.8, 2.8),
        ('学习路径', 5.8, 1.0),
    ]
    service_centers = []
    for label, x, y in services:
        _add_box(ax, (x, y), (2.0, 1.1), [label], '#FFFDF5', '#FF8C00')
        service_centers.append(y + 0.55)

    data_nodes = [
        ('数据库', '业务事务存储', 8.4, 8.2),
        ('缓存队列', 'Redis + Celery', 8.4, 6.4),
        ('向量库', 'Chroma + 嵌入', 8.4, 4.6),
        ('知识图谱', 'Neo4j', 8.4, 2.8),
        ('智能接口', 'DeepSeek', 8.4, 1.0),
    ]
    for name, desc, x, y in data_nodes:
        _add_box(ax, (x, y), (2.1, 1.1), [name, desc], '#F5F5FF', '#CD5C5C')

    _add_arrow(ax, (2.4, 7.6), (3.0, 7.6))
    for center_y in service_centers:
        _add_arrow(ax, (5.2, center_y), (5.8, center_y))
    service_right = 5.8 + 2.0
    for (_, _, x, y) in data_nodes:
        _add_arrow(ax, (service_right, y + 0.55), (x, y + 0.55))

    _save(fig, '系统流程图')


def data_flow():
    fig, ax = _create_canvas()
    top = [
        ('文档上传', '前端/知识库', 0.4),
        ('合法校验', '格式与权限', 2.1),
        ('任务调度', 'Celery 分发', 3.8),
        ('文本提取', '解析 + 分块', 5.5),
        ('向量入库', 'Chroma', 7.2),
        ('索引完成', '供检索调用', 8.9),
    ]
    for idx, (title, desc, x) in enumerate(top):
        _add_box(ax, (x, 7.2), (1.4, 1.1), [title, desc], '#E8F4FF', '#1E90FF')
        if idx > 0:
            _add_arrow(ax, (top[idx - 1][2] + 1.4, 7.75), (x, 7.75))

    qa_flow = [
        ('提问入口', '问题/知识库', 0.6),
        ('语义理解', 'QA 服务', 2.4),
        ('RAG 检索', '向量 + 兜底', 4.2),
        ('答案生成', 'DeepSeek', 6.0),
        ('用户响应', '答案/引用', 7.8),
    ]
    for idx, (title, desc, x) in enumerate(qa_flow):
        _add_box(ax, (x, 4.4), (1.6, 1.1), [title, desc], '#FFF8E1', '#FF8C00')
        if idx > 0:
            _add_arrow(ax, (qa_flow[idx - 1][2] + 1.6, 4.95), (x, 4.95))

    _add_arrow(ax, (6.6, 7.2), (5.0, 5.5))
    _add_arrow(ax, (5.0, 4.4), (6.6, 6.9))

    bottom = [
        ('课表接口', '提醒触发', 2.2),
        ('学习路径', '掌握度更新', 4.4),
        ('数据分析', '日志/告警', 6.6),
    ]
    for (title, desc, x) in bottom:
        _add_box(ax, (x, 1.5), (1.8, 1.1), [title, desc], '#F0FFF0', '#2E8B57')
    _add_arrow(ax, (qa_flow[0][2] + 0.8, 4.4), (bottom[0][2] + 0.9, 2.6))
    _add_arrow(ax, (qa_flow[1][2] + 0.8, 4.4), (bottom[1][2] + 0.9, 2.6))
    _add_arrow(ax, (qa_flow[2][2] + 0.8, 4.4), (bottom[2][2] + 0.9, 2.6))

    _save(fig, '数据流图')


def software_structure():
    fig, ax = _create_canvas(width=10, height=8)
    layers = [
        ('表现层', 'Web、移动、教务助手', 8.5),
        ('接口层', 'DRF 路由、JWT、安全策略', 7.0),
        ('领域服务层', '认证 / 课表 / 知识库 / 问答 / 学习路径', 5.5),
        ('集成层', 'Celery、向量服务、Neo4j 适配、通知总线', 4.0),
        ('数据层', 'MySQL、Redis、Chroma、Neo4j、对象存储', 2.5),
    ]
    for title, desc, y in layers:
        _add_box(
            ax,
            (0.7, y - 0.6),
            (8.6, 1.2),
            [title, desc],
            '#FFFFFF',
            '#4C72B0',
            linewidth=2.0,
            radius=0.3,
        )
    ax.text(
        5,
        9.5,
        '软件结构分层',
        ha='center',
        va='center',
        fontproperties=DEFAULT_FONT,
        fontsize=20,
        color='#111111',
    )
    _save(fig, '软件结构图')


def main():
    system_flow()
    data_flow()
    software_structure()


if __name__ == '__main__':
    main()
